<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>

</body>
<script src="../../../jquery-1.12.4.min.js"></script>
<script>
    // 【核心】本篇文章的核心是为了介绍jq的each，同时模仿源码自己实现。
    // （1）jq的each区别foreach的特点：jq的each是可以跳出循环的，而foreach只能跳出当前迭代。
    // （2）js的for循环是可以跳出当前迭代的：使用break；

    // 【1】each介绍
    // jQuery 的 each 方法，作为一个通用遍历方法，可用于遍历对象和数组。
    // 遍历对象
    $.each({
        name: "John",
        lang: "JS"
    }, function(i, n) {
        console.log("Name: " + i + ", Value: " + n);
    });
    // Name: name, Value: John
    // Name: lang, Value: JS
    // 【2】退出循环
    // 尽管 ES5 提供了 forEach 方法，但是 forEach 没有办法中止或者跳出 forEach 循环，除了抛出一个异常。
    [10, 11, 12, 13, 14, 15].forEach(function(v, i) {
        //注意这个地方的函数的默认参数顺序
        if (i == 1) return false;
        console.log(v);
        console.log(i);
    })
    console.log("分割线");
    // 但是对于 jQuery 的 each 函数，如果需要退出 each 循环可使回调函数返回 false，其它返回值将被忽略。
    $.each([10, 11, 12, 13, 14, 15], function(i, v) {
        if (i == 2) return false;
        console.log("Item #" + i + ": " + v);
    });

    // Item #0: 0
    // Item #1: 1
    // Item #2: 2

    //【3】第一版
    // 首先，我们肯定要根据参数的类型进行判断，如果是数组，就调用 for 循环，如果是对象，
    // 就使用 for in 循环，有一个例外是类数组对象，对于类数组对象，我们依然可以使用 for 循环。

    // 那么又该如何判断类数组对象和数组呢？实际上，我们在[《JavaScript专题之类型判断(下)》]
    // (https://github.com/mqyqingfeng/Blog/issues/30)就讲过jQuery 数组和类数组对象判断函数 isArrayLike 的实现。
    function each(obj, callback) {
        var length, i = 0;

        if (isArrayLike(obj)) {
            length = obj.length;
            for (; i < length; i++) {
                callback(i, obj[i])
            }
        } else {
            for (i in obj) {
                callback(i, obj[i])
            }
        }

        return obj;
    }
    //【4】中止循环
    // 现在已经可以遍历对象和数组了，但是依然有一个效果没有实现，就是中止循环，按照 jQuery each 的实现，
    // 当回调函数返回 false 的时候，我们就中止循环。这个实现起来也很简单：

    // 我们只用把：
    callback(i, obj[i])

    // 替换成：
    if (callback(i, obj[i]) === false) {
        break;
    };
    // 【5】this
    // 我们在实际的开发中，我们有时会在 callback 函数中用到 this，先举个不怎么恰当的例子：
    // 我们给每个人添加一个 age 属性，age 的值为 18 + index
    var person = [{
        name: 'kevin'
    }, {
        name: 'daisy'
    }]
    $.each(person, function(index, item) {
        this.age = 18 + index;
    })

    console.log(person);
    // 这个时候，我们就希望 this 能指向当前遍历的元素，然后给每个元素添加 age 属性。
    // 指定 this，我们可以使用 call 或者 apply，其实也很简单：

    // 我们把：
    if (callback(i, obj[i]) === false) {
        break;
    }
    // 替换成：
    if (callback.call(obj[i], i, obj[i]) === false) {
        break;
    }
    // 【5-1】$.each($("p")和$("p").each区别
    // 因为 $("p").each() 方法是定义在 jQuery 函数的 prototype 对象上面的， 而 $.each() 方法是定义 jQuery 函数上面的， 
    // 调用的时候不从复杂的 jQuery 对象上调用， 速度快得多。所以我们推荐使用$.each($("p")。
    // 【5-2】所以最终的 each 源码为：
    function each(obj, callback) {
        var length, i = 0;

        if (isArrayLike(obj)) {
            length = obj.length;
            for (; i < length; i++) {
                if (callback.call(obj[i], i, obj[i]) === false) {
                    break;
                }
            }
        } else {
            for (i in obj) {
                if (callback.call(obj[i], i, obj[i]) === false) {
                    break;
                }
            }
        }

        return obj;
    }
    //【6】性能比较
    // 我们在性能上比较下for 循环和 each 函数：
    var arr = Array.from({
        length: 1000000
    }, (v, i) => i);

    console.time('for')
    var i = 0;
    for (; i < arr.length; i++) {
        i += arr[i];
    }
    console.timeEnd('for')


    console.time('each')
    var j = 0;
    $.each(arr, function(index, item) {
        j += item;
    })
    console.timeEnd('each')
        // 运算结果显示：for循环的性能是明显好于 each 函数的，each 函数本质上也是用的 for 循环，到底是慢在了哪里呢？
        // 【6-1】再看一个例子
    function each(obj, callback) {
        var i = 0;
        var length = obj.length
        for (; i < length; i++) {
            value = callback(i, obj[i]);
        }
    }

    function eachWithCall(obj, callback) {
        var i = 0;
        var length = obj.length
        for (; i < length; i++) {
            value = callback.call(obj[i], i, obj[i]);
        }
    }

    var arr = Array.from({
        length: 1000000
    }, (v, i) => i);

    console.time('each')
    var i = 0;
    each(arr, function(index, item) {
        i += item;
    })
    console.timeEnd('each')


    console.time('eachWithCall')
    var j = 0;
    eachWithCall(arr, function(index, item) {
        j += item;
    })
    console.timeEnd('eachWithCall');
    // eachWithCall的打印时间大约是each的一倍多
    // each 函数和 eachWithCall 函数唯一的区别就是 eachWithCall 调用了 call，从结果我们可以推测出，
    // call 会导致性能损失，但也正是 call 的存在，我们才能将 this 指向循环中当前的元素。
</script>

</html>

</html>